import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;

/**
 * Created by walker on 2017/5/16.
 *
 */
public class FileUtil {
    private static ConcurrentHashMap<String, List<String>> resultmap = new ConcurrentHashMap<>(); //文件重复判断结果map
    private static HashMap<Long, List<String>> filesizemap = new HashMap<>(); //文件大小map
    private static HashSet<File> emptyfileset = new HashSet<>(); //空文件set
    private static int dirNum = 0; //文件夹总数量
    private static int fileNum = 0; //文件总数量
    private static int threadNum = 0;//所开线程数量
    private static ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>(); //线程安全的队列
    private static CountDownLatch latch;

    public static void setThreadNum(int threadNum) {
        FileUtil.threadNum = threadNum;
    }

    public static String takeFile() { //要确保多线程取资源时线程安全
        String file = queue.poll();
        if (file == null && queue.isEmpty()) {
            return null;
        } else {
            return file;
        }
    }

    public static void countDown() {
        latch.countDown();
    }

    /**
     * 计算文件MD5值,并存到resultmap,多线程存，要确保resultmap线程安全
     * @param filepath
     * @return
     */
    public static void computeFileMD5AndSaveResult(String filepath) {
        File file = new File(filepath);
        String md5 = MD5Util.getFileMD5(file);
        System.out.println("文件_" + filepath + "的MD5计算完成，值为_" + md5);
        if (resultmap.containsKey(md5)) {
            resultmap.get(md5).add(file.getAbsolutePath());
        } else {
            List<String> files = new ArrayList<>();
            files.add(file.getAbsolutePath());
            resultmap.put(md5, files);
        }
    }

    /**
     *使用多线程分批计算文件MD5
     */
    public static void useMutiThread(int threadNum) {
        latch = new CountDownLatch(threadNum);

        int allThreadTaskNum = 0;
        for (List<String> files : filesizemap.values()) {
            if (files.size() > 1) {
                allThreadTaskNum++;
            }
        }

        System.out.println("分配执行总数：" + allThreadTaskNum);

        MD5Thread[] threads = new MD5Thread[threadNum];
        for (int i=0; i<threads.length; i++) {
            threads[i] = new MD5Thread(latch);
        }

        FileUtil.product();

        for (MD5Thread md5Thread: threads){
            md5Thread.start();
        }

        try {
            latch.await(); //不阻塞main线程，输出有错
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    /**
     * 队列添加待计算文件
     */
    public static void product() {
        for (List<String> files : filesizemap.values()) { //遍历filesizemap
            if (files.size() > 1) {
                for (String file : files) {
                    queue.offer(file);
                }
            } else {
                String key = "singleCount_" + files.get(0);
                resultmap.put(key, files);
            }
        }
    }

    /**
     *选择是否使用多线程方法
     * @param flag true为使用多线程方法
     */
    public static void choseMethod(boolean flag) {
        if (flag) {
            FileUtil.setThreadNum(4);
            useMutiThread(threadNum);
        } else {
            for (List<String> files : filesizemap.values()) {
                if (files.size() > 1) {
                    for (String file : files) {
                        computeFileMD5AndSaveResult(file);
                    }
                } else {
                    String key = "singleCount_" + files.get(0);
                    resultmap.put(key, files);
                }
            }
        }
    }

    /**
     * 文件目录递归，生成filesizemap
     * @param fileList 用户上传文件夹集合
     */
    public static void fileHandle(File[] fileList) {
        for (int i=0; i<fileList.length; i++) {
            if (!fileList[i].exists()) {
                System.out.println("目录：" + fileList[i] + "不存在");
                continue;
            }
            if (fileList[i].isDirectory()) { //如果是目录
                dirNum++;
                File[] subdirs = fileList[i].listFiles(); //获取其子目录
                if (subdirs != null){
                    if (subdirs.length == 0) { //目录为空
                        emptyfileset.add(fileList[i]);
                        continue;
                    }
                    fileHandle(subdirs); //对非空子目录递归调用
                }
            } else {
                fileNum++;
                long size = fileList[i].length();
                if (filesizemap.containsKey(size)) {
                    filesizemap.get(size).add(fileList[i].getAbsolutePath());
                } else {
                    List<String> files = new ArrayList<>();
                    files.add(fileList[i].getAbsolutePath());
                    filesizemap.put(size, files);
                }
            }
        }
    }

    public static void main(String[] args) {
        String pathname = "D:/电影";
        File[] fileList = new File[]{new File(pathname)};
        long start = System.currentTimeMillis();
        fileHandle(fileList);
        long listdirend = System.currentTimeMillis();
        FileUtil.choseMethod(true);
        long end = System.currentTimeMillis();
        int num = 0;
        /* 写入Txt文件 */
        String resultFile = "C:/Users/walker/Desktop/" + new File(pathname).getName() + "查重结果.txt";
        System.out.println(resultFile);
        File writename = new File(resultFile); // 绝对路径，如果没有则要建立一个新的文件
        try {
            writename.createNewFile(); // 创建新文件
            BufferedWriter out = new BufferedWriter(new FileWriter(writename));
            out.write("遍历查重" + pathname + "的结果如下：\r\n");
            out.write("共扫描" + dirNum + "个目录\r\n");
            out.write("共扫描" + fileNum + "个文件\r\n");
            out.write("递归目录平均耗时为: " + ((listdirend - start) / dirNum) + "毫秒\r\n");
            out.write("递归目录总耗时为: " + ((listdirend - start) / 1000.0) + "秒\r\n");
            out.write("计算文件MD5值耗时为：" + ((end - listdirend) / 1000.0) + "秒\r\n");
            out.write("总耗时为：" + ((end - start) / 1000.0) + "秒\r\n");
            out.write("\r\n");
            out.write("重复文件如下：\r\n");
            out.write("\r\n");

            for (Map.Entry<String, List<String>> entry : resultmap.entrySet()) {
                if (entry.getValue().size() > 1){
                    for (String path : entry.getValue()) {
                        out.write(path + "\r\n");
                    }
                    out.write("\r\n");
                    num++;
                }
            }
            out.write("共有" + num + "组重复文件\r\n");
            out.flush(); // 把缓存区内容压入文件
            out.close(); // 最后记得关闭文件
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("共有" + dirNum + "个目录");
        System.out.println("共有" + fileNum + "个文件");
        System.out.println("递归目录平均耗时为: " + ((listdirend - start) / dirNum) + "毫秒");
        System.out.println("递归目录总耗时为: " + ((listdirend - start) / 1000.0) + "秒");
        System.out.println("计算文件MD5值耗时为：" + ((end - listdirend) / 1000.0) + "秒");
        System.out.println("总耗时为：" + ((end - start) / 1000.0) + "秒");
        System.out.println("空文件有"+emptyfileset.toString());
        System.out.println("共有" + num + "组重复文件");
    }
}